package cn.lingyangwl.agile.auth.oauth2.model;

import cn.hutool.core.util.BooleanUtil;
import cn.lingyangwl.agile.model.module.auth.GrantTypeEnum;
import cn.lingyangwl.agile.auth.model.entity.OAuth2Client;
import cn.lingyangwl.agile.model.constants.AppType;
import cn.lingyangwl.framework.tool.core.exception.BizException;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.time.Duration;
import java.time.Instant;
import java.util.*;

/**
 * @author shenguangyang
 */
@Data
@AllArgsConstructor
@Builder
@Accessors(chain = true)
@NoArgsConstructor
public class RegisteredClient {
    /**
     * 刷新令牌有效期默认 30 天
     */
    private final static int refreshTokenValiditySeconds = 60 * 60 * 24 * 30;

    /**
     * 请求令牌有效期默认 12 小时
     */
    private final static int accessTokenValiditySeconds = 60 * 60 * 12;

    private String clientId;
    private Instant clientIdIssuedAt;
    private String clientSecret;
    private Instant clientSecretExpiresAt;
    private String clientName;
    /**
     * 是否使用验证码
     */
    private Boolean useCaptcha;

    /**
     * 是否允许同时在线
     */
    private Boolean canSameTimeOnline;

    private final List<String> scopes = new ArrayList<>();

    private final Set<String> authorizationGrantTypes = new HashSet<>();

    /**
     * 访问令牌过期实际
     */
    private Duration accessTokenTimeToLive = Duration.ofMinutes(5);

    /**
     * 刷新令牌过期时间
     */
    private Duration refreshTokenTimeToLive = Duration.ofMinutes(20);

    /**
     * 登录失败次数, 当登录失败达到一定次数之后, 就会被锁定 <br/>
     * -1则不限制
     */
    private Integer loginFailCount;

    /**
     * 锁定间隔(单位为秒)
     */
    private Integer lockInterval;

    /**
     * 连续被锁定时间乘子, 下一次锁定的时间 = 连续被锁定次数 * 锁定间隔 * 锁定时间乘子
     */
    private Integer lockMultiplier;

    /**
     * 原始客户端信息
     */
    private OAuth2Client rawClient;

    /**
     * 应用类型
     */
    private AppType appType;

    public static RegisteredClient of(OAuth2Client client) {
        if (Objects.isNull(client)) {
            throw new BizException("客户端不存在, 请联系管理员");
        }
        RegisteredClient registeredClient = new RegisteredClient();
        registeredClient.rawClient = client;

        // 授权模式
        Optional.ofNullable(client.getAuthorizedGrantTypes())
                .ifPresent(grants -> new HashSet<>(grants)
                        .forEach(registeredClient::authorizationGrantType));
        // scope
//        Optional.ofNullable(client.getScope()).ifPresent(
//                scope -> scope.stream().filter(StrUtil::isNotBlank).forEach(registeredClient::scope));

        return registeredClient
                .setClientId(client.getClientId()).setClientSecret(client.getClientSecret())
                .setLockInterval(client.getLockInterval()).setLockMultiplier(client.getLockMultiplier())
                .setLoginFailCount(client.getLoginFailCount())
                .setAppType(AppType.ofByCode(client.getAppType()))
                .setUseCaptcha(BooleanUtil.toBooleanObject(client.getUseCaptcha()))
                .setCanSameTimeOnline(BooleanUtil.toBooleanObject(client.getCanSameTimeOnline()))
                .setAccessTokenTimeToLive(Duration.ofSeconds(Optional
                        .ofNullable(client.getAccessTokenValidity()).orElse(accessTokenValiditySeconds)))
                .setRefreshTokenTimeToLive(Duration.ofSeconds(Optional.ofNullable(client.getRefreshTokenValidity())
                        .orElse(refreshTokenValiditySeconds)))
                .authorizationGrantType(GrantTypeEnum.AUTHORIZATION_CODE.getCode())
                .authorizationGrantType(GrantTypeEnum.REFRESH_TOKEN.getCode())
                .authorizationGrantType(GrantTypeEnum.CLIENT_CREDENTIALS.getCode());
    }

    public RegisteredClient authorizationGrantType(String authorizationGrantType) {
        this.authorizationGrantTypes.add(authorizationGrantType);
        return this;
    }

    public RegisteredClient scope(String scope) {
        this.scopes.add(scope);
        return this;
    }

    /**
     * 是否有限制登录失败
     */
    public boolean hasLimitLoginFail() {
        return Objects.nonNull(loginFailCount) && loginFailCount != -1;
    }

}
